_ = require 'lodash'

error = require '../error'
util = require '../util'
log = require '../log'

mongo = require '../storage/Mongo'
Meta = require '../meta/Meta'

toDupKeyError = (e, entityMeta)->
    log.debug 'toDupKeyError, message', e.message
    # E11000 duplicate key error index: fcms.F_User.$F_User_nickname dup key: { : "yyyy" }
    matches = e.message.match(/index:\s(.+)\$(.+) dup key: (.+)/)
    if matches
        indexName = matches[2]
        # value = matches[3]
        log.debug 'toDupKeyError, indexName=' + indexName

        indexConfig = _.find entityMeta.mongoIndexes, (i)-> entityMeta.tableName + "_" + i.name == indexName
        log.system.warn 'No index config for ' + indexName unless indexConfig
        message = indexConfig?.errorMessage || "值重复：#{indexName}"
        {code: "DupKey", message, key: indexName}
    else
        {code: "DupKey", message: e.message, key: null}

exports.gCreate = (entityMeta, instance)->
    # ObjectId 或非 String 的 id 前面设置，这里自动设置 String 类型的 ID
    if entityMeta.fields._id.persistType == 'String' and not instance._id?
        instance._id = Meta.newObjectId().toString()

    db = yield mongo.pDatabase()
    c = db.collection entityMeta.tableName
    try
        res = yield c.insertOne instance
        mongo.getInsertedIdObject(res)
    catch e
        throw e unless mongo.isIndexConflictError(e)
        {code, message} = toDupKeyError(e, entityMeta)
        throw new error.UniqueConflictError(code, message)

# 全量/部分更新一个或多个
exports.gUpdateMany = (entityMeta, idVersions, instance)->
    db = yield mongo.pDatabase()
    c = db.collection entityMeta.tableName

    set = {}
    unset = {}

    for key, value of instance
        if value?
            set[key] = value
        else
            unset[key] = ""

    # log.debug 'set', set, 'unset', unset

    try
        for idVersion in idVersions
            criteria = {_id: idVersion._id, _version: idVersion._version}
            update2 = {$inc: {_version: 1}}
            update2.$set = set if _.size(set)
            update2.$unset = unset if _.size(unset)
            res = yield c.updateOne criteria, update2
            r = mongo.getUpdateResult(res)
            if r.modifiedCount != 1
                throw new error.UserError 'ConcurrentUpdate'
    catch e
        throw e unless mongo.isIndexConflictError(e)
        {code, message} = toDupKeyError(e, entityMeta)
        throw new error.UniqueConflictError(code, message)

exports.gNativeUpdate = (entityMeta, criteria, change)->
    db = yield mongo.pDatabase()
    c = db.collection entityMeta.tableName

    try
        res = yield c.updateOne criteria, change
        r = mongo.getUpdateResult(res)
        if r.modifiedCount != 1
            log.system.warn 'ConcurrentUpdate', {entity: entityMeta.name, modifiedCount: r.modifiedCount}
            throw new error.UserError 'ConcurrentUpdate'
    catch e
        throw e unless mongo.isIndexConflictError(e)
        {code, message} = toDupKeyError(e, entityMeta)
        throw new error.UniqueConflictError(code, message)

# 批量删除
exports.gRemoveMany = (entityMeta, ids)->
    r = if entityMeta.removeMode == 'toTrash'
        yield from gRemoveManyToTrash(entityMeta, ids)
    else
        yield from gRemoveManyCompletely(entityMeta, ids)

# 批量找回
exports.gRecoverMany = (entityMeta, ids)->
    trashTable = Meta.getCollectionName(entityMeta, "trash")

    db = yield mongo.pDatabase()
    formalCollection = db.collection entityMeta.tableName
    trashCollection = db.collection trashTable

    list = yield trashCollection.find({_id: {$in: ids}}).toArray()
    for entity in list
        entity.modifiedOn = new Date()
        entity._version++

    try
        yield formalCollection.insertMany(list)
    catch e
        throw e unless mongo.isIndexConflictError(e)
        message = buildErrorMessageForDupKey(e)
        throw new error.UserError {code: 'DupKey', message: message}

    yield trashCollection.deleteMany({_id: {$in: ids}})

exports.gFindOneByCriteria = (repo, entityMeta, criteria, includedFields)->
    collectionName = Meta.getCollectionName entityMeta, repo

    mCriteria = {}
    mongo.toMongoCriteria(criteria, mCriteria)

    db = yield mongo.pDatabase()
    c = db.collection collectionName
    projection = util.arrayToTrueObject(includedFields)
    return yield c.findOne mCriteria, projection || {}

exports.gList = ({repo, entityMeta, criteria, includedFields, sortCriteria, pageNo, pageSize, withoutTotal})->
    collectionName = Meta.getCollectionName entityMeta, repo

    mCriteria = {}
    mongo.toMongoCriteria(criteria, mCriteria)
    # log.debug 'mc', mCriteria

    mSort = {}
    mSort[field] = order && -1 || 1 for field, order of sortCriteria # true 是倒序

    db = yield mongo.pDatabase()
    c = db.collection collectionName

    unless withoutTotal
        total = yield c.count(mCriteria)

    includedFields = util.arrayToTrueObject(includedFields) if includedFields

    cursor = c.find(mCriteria, includedFields || {}).sort(mSort)
    # 判定是否分页
    cursor.skip((pageNo - 1) * pageSize).limit(pageSize) if pageSize > 0

    page = yield cursor.toArray()
    # log.debug 'page', page

    if withoutTotal
        page
    else
        {total, page}

###
    软删除有几种方式：放在单独的表中，放在原来的表中+使用标记字段。
    放在单独的表中，在撤销删除后，有id重复的风险：例如删除id为1的实体，其后又产生了id为1的实体，则把删除的实体找回后就主键冲突了
    好在目前采用ObjectId的方式不会导致该问题。
    放在原表加标记字段的方式，使得所有的查询都要记得查询删除标记为false的实体，并影响索引的构建，麻烦
###
gRemoveManyToTrash = (entityMeta, ids) ->
    trashTable = Meta.getCollectionName(entityMeta, "trash")

    db = yield mongo.pDatabase()
    formalCollection = db.collection entityMeta.tableName
    trashCollection = db.collection trashTable

    list = yield formalCollection.find({_id: {$in: ids}}).toArray()
    for entity in list
        entity.modifiedOn = new Date()
        entity._version++

    yield trashCollection.insertMany(list)

    yield formalCollection.deleteMany(filter)

gRemoveManyCompletely = (entityMeta, ids) ->
    db = yield mongo.pDatabase()
    c = db.collection entityMeta.tableName
    yield c.deleteMany {_id: {$in: ids}}

test = ->
    require('co')(->
        try
            meta = require('../meta/Meta')
            yield from meta.gLoad()
            yield from exports.gCreate(meta.getEntityMeta("F_User"), {nickname: 'yyyy'})
        catch e
            log.system.error e, e.message, "test"
        finally
            yield from require('../storage/Mongo').gDispose()
    ).catch (e)-> log.system.error e, "test"
